﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Demo.Framework.Types
{
    class Program
    {
        static void Main(string[] args)
        {
            // Value Types
            
            Console.WriteLine("Value Type");
            
            int valueType1 = 1; // stored on stack
            int valueType2 = valueType1;

            Console.WriteLine(valueType1);
            Console.WriteLine(valueType2);

            valueType1 = 2;

            Console.WriteLine(valueType1);
            Console.WriteLine(valueType2);

            Method(valueType1);

            Console.WriteLine(valueType1);
            Console.WriteLine(valueType2);

            // Reference Type

            Console.WriteLine("Reference Type");

            var referenceType1 = new MyClass() { Id = "1" }; // stored on heap
            var referenceType2 = referenceType1;

            Console.WriteLine(referenceType1.ToString());
            Console.WriteLine(referenceType2.ToString());

            referenceType1.Id = "2";

            Console.WriteLine(referenceType1.ToString());
            Console.WriteLine(referenceType2.ToString());

            Method(referenceType1);

            Console.WriteLine(referenceType1.ToString());
            Console.WriteLine(referenceType2.ToString());

            // Boxing - Unboxing

            int a = 1; object b = new object();

            /* Boxing - Converting vaue type to reference type

            1. Memory is allocated from the managed heap. The amount of memory allocated is the size
                required by the value type’s fields plus the two additional overhead members (the type object
                pointer and the sync block index) required by all objects on the managed heap.
            2. The value type’s fields are copied to the newly allocated heap memory.
            3. The address of the object is returned. This address is now a reference to an object; the value
                type is now a reference type.
             
             */
            b = (object)a;

            // Unboxing - Converting reference type to value type
            /*
             * Unboxing is not the exact opposite of boxing. The unboxing operation is much less costly than
            boxing. Unboxing is really just the operation of obtaining a pointer to the raw value type (data fields)
            contained within an object. In effect, the pointer refers to the unboxed portion in the boxed instance.
            So, unlike boxing, unboxing doesn’t involve the copying of any bytes in memory. Having made this
            important clarification, it is important to note that an unboxing operation is typically followed by
            copying the fields.
            Obviously, boxing and unboxing/copy operations hurt your application’s performance in terms of
            both speed and memory, so you should be aware of when the compiler generates code to perform
            these operations automatically and try to write code that minimizes this code generation.
             * 
             * */
            a = (int)b;

            // Difference between .ToString() and Convert.toString() -- Convert.ToString() handles null
            MyClass c = null;

            try
            {
                Console.WriteLine(c.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            try
            {
                Console.WriteLine(Convert.ToString(c));
            }
            catch (Exception ex)
            {                
                Console.WriteLine(ex.Message);
            }

            // Test what happens when you add null type to string
            string s1 = null;
            string s2 = "My test String";
            // When a null string is added to a valid string, the result is the valid string
            string s3 = s1 + s2;

            // this will give null reference exception as s1 is null
            Console.WriteLine(s3 + s1.Length);

            Console.Read();
        }

        static void Method(int i)
        {
            i = 99;
        }    

        static void Method(MyClass c)
        {
            c.Id = "99"; // changes the parent class
            //c = new MyClass() { Id = "99" }; // no change on parent class
            //c = null;
        }
    }

    public class MyClass
    {
        public string Id { get; set; }
        public override string ToString()
        {
            return Id;
        }
    }
}
